package com.lab;

import com.lab.dag.DAGRunnable;
import com.lab.forthreadpool.*;

import java.math.BigDecimal;
import java.security.Security;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author weiwei
 */
public class NoobChain {
    //线程安全
    public static List<Block> blockchain = Collections.synchronizedList(new ArrayList<Block>());
    public static Lock[] locks; //钱包锁
    public static Object obj = new Object();//区块锁
    //线程安全
    public static ConcurrentHashMap<String, TransactionOutput> UTXOs =
            new ConcurrentHashMap<String, TransactionOutput>();
    public static int difficulty = 4;
    public static double minimumTransaction = 0.1f;
    //    public static Wallet walletA;
//    public static Wallet walletB;
    public static Transaction genesisTransaction;
    public static Scanner scanner = new Scanner(System.in);
    public static List<Wallet> wallets = null;
    public static Block genesis;
    public static long startTime = Long.MAX_VALUE;
    public static long endTime = Long.MIN_VALUE;


    public static int testNum = 900; //交易次数

    public static int n = 20; //钱包个数

    public static int sleepTime = 120000;

    public static int coreThread = 20;

    public static int maxThread = 100;

    public static void main(String[] args) {
        testThreadPool();
//        testDAG();
//        System.out.println(startTime);
//        System.out.println(endTime);

        System.out.println("程序运行时间：" + (endTime - startTime) + "ms");
    }

    public static boolean tryGetLock(int A, int B) {
        if (locks[A].tryLock()) {
            if (locks[B].tryLock()) {
                return true;
            } else {
                locks[A].unlock();
                return false;
            }
        } else {
            return false;
        }
    }

    public static void testThreadPool() {
        //add our blocks to the blockchain ArrayList:
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Setup Bouncey castle as a Security Provider


        //初始化钱包锁
        locks = new Lock[n];
        for (int i = 0; i < n; i++) {
            locks[i] = new ReentrantLock();
        }

        //初始化钱包
        wallets = Collections.synchronizedList(new ArrayList<Wallet>());
        for (int i = 0; i < n; i++) {
            wallets.add(new Wallet());
        }


//        System.out.println("请输入钱包初始化value：");
//        double val = scanner.nextdouble();
        double val = 1000000;
        List<Double> compare = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            compare.add(val);
        }


        genesis = new Block("0");

        for (int i = 0; i < n; i++) {
            Wallet coinbase = new Wallet();
            //给每个钱包传过去val
            genesisTransaction = new Transaction(coinbase.publicKey, wallets.get(i).publicKey,
                    val, null);
            genesisTransaction.generateSignature(coinbase.privateKey);     //生成签名
            genesisTransaction.transactionId = "0"; //交易id为0
            //设置output数组
            genesisTransaction.outputs.add(new TransactionOutput
                    (genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId));
            //its important to store our first transaction in the UTXOs list.
            UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));

            //区块中传入交易类
            genesis.addTransaction(genesisTransaction);
        }

        addBlock(genesis);

        //测试开始
        //线程池初始化
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                coreThread,
                maxThread,
                1,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                new ThreadPoolExecutor.CallerRunsPolicy());

        Random random = new Random();//随机数
        for (int i = 0; i < testNum; i++) {
            int A = random.nextInt(n);
            int B = random.nextInt(n);
            if (B == A) {
                if (A == 0) {
                    B++;
                } else {
                    B--;
                }
            }

            val = random.nextDouble() * 100 + 0.1f;
            BigDecimal bg = new BigDecimal(val);
            val = bg.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();


            double va = compare.get(A);
            va -= val;
            double vb = compare.get(B);
            vb += val;

            compare.set(A, va);
            compare.set(B, vb);

            System.out.println(A + " -> " + B + " " + val);
            MyRunnable myRunnable = new MyRunnable(A, B, val);
            executor.execute(myRunnable);
        }

        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        double total = 0;
        System.out.println("打印所有钱包balance：");
        for (int i = 0; i < n; i++) {
            double bal = wallets.get(i).getBalance();
            System.out.println(i + "th's balance is: " + bal + "  compare: " + compare.get(i) + " " + (Math.abs(compare.get(i) - bal) <= 0.001));
            total += bal;
        }

        System.out.println("总共：" + total);
        System.out.println("区块数：" + NoobChain.blockchain.size());

        isChainValidForThreadPool();

    }

    public static void testDAG() {
        //add our blocks to the blockchain ArrayList:
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Setup Bouncey castle as a Security Provider


        //初始化钱包锁
        locks = new Lock[n];
        for (int i = 0; i < n; i++) {
            locks[i] = new ReentrantLock();
        }

        //初始化钱包
        wallets = Collections.synchronizedList(new ArrayList<Wallet>());
        for (int i = 0; i < n; i++) {
            wallets.add(new Wallet());
        }

        double val = 1000000;
        List<Double> compare = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            compare.add(val);
        }


        genesis = new Block("0");

        for (int i = 0; i < n; i++) {
            Wallet coinbase = new Wallet();
            //给每个钱包传过去val
            genesisTransaction = new Transaction(coinbase.publicKey, wallets.get(i).publicKey,
                    val, null);
            genesisTransaction.generateSignature(coinbase.privateKey);     //生成签名
            genesisTransaction.transactionId = "0"; //交易id为0
            //设置output数组
            genesisTransaction.outputs.add(new TransactionOutput
                    (genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId));
            //its important to store our first transaction in the UTXOs list.
            UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));

            //区块中传入交易类
            genesis.addTransaction(genesisTransaction);
        }

        addBlock(genesis);

        //测试开始
        //线程池初始化
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                coreThread,
                maxThread,
                1,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                new ThreadPoolExecutor.CallerRunsPolicy());

        Random random = new Random();//随机数
        for (int i = 0; i < testNum; i++) {
            int A = random.nextInt(n);
            int B = random.nextInt(n);
            if (B == A) {
                if (A == 0) {
                    B++;
                } else {
                    B--;
                }
            }

            val = random.nextDouble() * 100 + 0.1f;
            BigDecimal bg = new BigDecimal(val);
            val = bg.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();


            double va = compare.get(A);
            va -= val;
            double vb = compare.get(B);
            vb += val;

            compare.set(A, va);
            compare.set(B, vb);

//            System.out.println(A + " -> " + B + " " + val);
            DAGRunnable dagRunnable = new DAGRunnable(A, B, val);
            executor.execute(dagRunnable);
        }

        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        double total = 0;
        System.out.println("打印所有钱包balance：");
        for (int i = 0; i < n; i++) {
            double bal = wallets.get(i).getBalance();
            System.out.println(i + "th's balance is: " + bal + "  compare: " + compare.get(i) + " " + (Math.abs(compare.get(i) - bal) <= 0.001));
            total += bal;
        }

        System.out.println("总共：" + total);

        System.out.println("区块数：" + NoobChain.blockchain.size());

        isChainValidForThreadPool();

    }


    public static Boolean isChainValidForThreadPool() {
        Block currentBlock;
        Block previousBlock;
        String hashTarget = new String(new char[difficulty]).replace('\0', '0');
        HashMap<String, TransactionOutput> tempUTXOs = new HashMap<String, TransactionOutput>();
        //loop through blockchain to check hashes:
        for (int i = 1; i < blockchain.size(); i++) {

            currentBlock = blockchain.get(i);
            previousBlock = blockchain.get(i - 1);
            //compare registered hash and calculated hash:
            if (!currentBlock.hash.equals(currentBlock.calculateHash())) {
                System.out.println("#Current Hashes not equal");
                return false;
            }
            //compare previous hash and registered previous hash
            if (!previousBlock.hash.equals(currentBlock.previousHash)) {
                System.out.println("#Previous Hashes not equal");
                return false;
            }
            //check if hash is solved
            if (!currentBlock.hash.substring(0, difficulty).equals(hashTarget)) {
                System.out.println("#This block hasn't been mined");
                return false;
            }
        }
        System.out.println("Blockchain is valid");
        return true;
    }

    public static void addBlock(Block newBlock) {
        newBlock.mineBlock(difficulty);
        blockchain.add(newBlock);
    }
}
